package com.orkun.api.converter.reader;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.orkun.api.converter.impl.AddressInfoModel;
import com.orkun.api.converter.utils.UnicodeBOMInputStream;
import com.orkun.api.converter.utils.Utility;

/**
 * XML reader implementation for AddressInfoModel
 */
public class ReaderXML implements Reader<AddressInfoModel> {

  /*
   * Parses from XML inputStream and returns an Arraylist. *
   */
  public ArrayList<AddressInfoModel> readData(DataInputStream dis) {
    // Printing the list of addresses obtained from XML
    SAXParserFactory parserFactor = SAXParserFactory.newInstance();
    SAXHandler handler = new SAXHandler();
    SAXParser parser;

    try {

      // BOM has to be skipped, if any
      // http://www.rgagnon.com/javadetails/java-handle-utf8-file-with-bom.html
      UnicodeBOMInputStream ubis = new UnicodeBOMInputStream(dis);
      ubis.skipBOM();
      parser = parserFactor.newSAXParser();
      InputStreamReader inputReader = new InputStreamReader(ubis, Utility.getEncoding());
      InputSource inputSource = new InputSource();
      inputSource.setCharacterStream(inputReader);
      // inputSource.setEncoding(Utility.getEncoding().toString());
      parser.parse(ubis, handler);
    } catch (ParserConfigurationException | SAXException | IOException e) {
      System.out.println("Error reading xml :" + e.getMessage());
      e.printStackTrace();
    }

    return handler.infoList;
  }

  /**
   * Method to be used in validation of the input XML file against an XSD file
   * 
   * @param filePath xsd file used for validation
   * @return true if valid
   */
  protected boolean validateSchema(String filePath) {
    Source xmlFile = new StreamSource(Utility.getInputStreamFromFile(filePath));
    InputStream xsdstream = Utility.getInputStreamFromFile("schema.xsd");
    Source schemaFile = new StreamSource(xsdstream);

    try {
      SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      Schema schema = schemaFactory.newSchema(schemaFile);

      Validator validator = schema.newValidator();
      validator.validate(xmlFile);
      System.out.println("XML file is valid");

    } catch (SAXException e) {
      System.out.println("XML file is NOT valid");
      System.out.println("Reason: " + e.getLocalizedMessage());
      return false;
    } catch (IOException e) {
      System.out.println("Error in xml validation");
      e.printStackTrace();
      return false;
    }
    return true;
  }

  // ref:
  // http://www.javacodegeeks.com/2013/05/parsing-xml-using-dom-sax-and-stax-parser-in-java.html
  class SAXHandler extends DefaultHandler {

    ArrayList<AddressInfoModel> infoList = new ArrayList<AddressInfoModel>();
    String cityname, citycode, districtname, zipcode;

    @Override
    // Triggered when the start of tag is found.
    public void startElement(String uri, String localName, String qName, Attributes attributes)
        throws SAXException {

      if (qName.equalsIgnoreCase(AddressInfoModel.CITY)) {
        cityname = attributes.getValue(AddressInfoModel.NAME);
        citycode = attributes.getValue(AddressInfoModel.CODE);
      }
      if (qName.equalsIgnoreCase(AddressInfoModel.DISTRICT)) {
        districtname = attributes.getValue(AddressInfoModel.NAME);
      }
      if (qName.equalsIgnoreCase(AddressInfoModel.ZIP)) {
        zipcode = attributes.getValue(AddressInfoModel.CODE);
        infoList.add(new AddressInfoModel(cityname, citycode, districtname, zipcode));
      }
    }
  }
}
